﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Data;

namespace GridPaging
{

    /// <summary>
    /// A customized <see cref="GridView"/> that provides a consistent look and feel
    /// </summary>    
    public class GridViewEx : GridView
    {
        #region Constructors

        /// <summary>
        /// Create a new instance of GridViewEx
        /// Force an override of the styles also.
        /// </summary>
        public GridViewEx()
            : base()
        {
            base.CssClass = "GridView";
            base.RowStyle.CssClass = base.CssClass + "Row";
            base.AlternatingRowStyle.CssClass = base.CssClass + "AlternatingRow";
            base.EmptyDataRowStyle.CssClass = base.CssClass + "EmptyData";
            base.HeaderStyle.CssClass = base.CssClass + "Header";
            base.FooterStyle.CssClass = base.CssClass + "Footer";
        }

        #endregion Constructors

        #region Public Properties

        /// <summary>
        /// The image for the back button on the pager
        /// </summary>
        [DefaultValue("~/images/left.gif"), Category("Appearance")]
        [Browsable(true)]
        public virtual String PagerBackImage
        {
            get
            {
                object obj = base.ViewState["PagerBackImage"];
                return (obj == null) ? "~/images/left.gif" : (String)obj;
            }
            set { base.ViewState["PagerBackImage"] = value; }
        }

        /// <summary>
        /// The image for the forward button on the pager
        /// </summary>
        [DefaultValue("~/images/right.gif"), Category("Appearance")]
        [Browsable(true)]
        public virtual String PagerForwardImage
        {
            get
            {
                object obj = base.ViewState["PagerForwardImage"];
                return (obj == null) ? "~/images/right.gif" : (String)obj;
            }
            set { base.ViewState["PagerForwardImage"] = value; }
        }

        /// <summary>
        /// The image to display when a column is sorted in ascending order
        /// </summary>
        [DefaultValue("~/Images/up.gif"), Category("Appearance")]
        [Browsable(true)]
        public virtual String SortAscendingImage
        {
            get
            {
                object obj = base.ViewState["SortAscendingImage"];
                return (obj == null) ? "~/Images/up.gif" : (String)obj;
            }
            set { base.ViewState["SortAscendingImage"] = value; }
        }

        /// <summary>
        /// The image to display when a column is sorted in descending order
        /// </summary>
        [DefaultValue("~/Images/down.gif"), Category("Appearance")]
        [Browsable(true)]
        public virtual String SortDescendingImage
        {
            get
            {
                object obj = base.ViewState["SortDescendingImage"];
                return (obj == null) ? "~/Images/down.gif" : (String)obj;
            }
            set { base.ViewState["SortDescendingImage"] = value; }
        }

        /// <summary>
        /// The sort expression for when the sorting is done in the bound object
        /// and not handled specifically in the grid
        /// </summary>
        [DefaultValue(""), Category("Data")]
        [Browsable(false)]
        public virtual String CustomSortExpression
        {
            get
            {
                object obj = base.ViewState["CustomSortExpression"];
                return (obj == null) ? "" : (String)obj;
            }
            set { base.ViewState["CustomSortExpression"] = value; }
        }

        /// <summary>
        /// The sort direction for when the sorting is done in the bound object
        /// and not handled specifically in the grid
        /// </summary>
        [DefaultValue(SortDirection.Ascending), Category("Data")]
        [Browsable(false)]
        public virtual SortDirection CustomSortDirection
        {
            get
            {
                object obj = base.ViewState["CustomSortDirection"];
                return (obj == null) ? SortDirection.Ascending : (SortDirection)obj;
            }
            set { base.ViewState["CustomSortDirection"] = value; }
        }

        /// <summary>
        /// Flag to indicate whether or not blank rows will be inserted if the number
        /// of actual rows to be displayed is less than the current page size
        /// </summary>
        [DefaultValue(false), Category("Behavior")]
        [Browsable(true)]
        public virtual Boolean FixedPageSize
        {
            get
            {
                object obj = base.ViewState["FixedPageSize"];
                return (obj == null) ? false : (Boolean)obj;
            }
            set { base.ViewState["FixedPageSize"] = value; }
        }

        /// <summary>
        /// Flag to indicate whether or not custom paging will be enabled. This will bypass
        /// the default paging mechanism for the GridView and allow us to perform SQL paging.
        /// </summary>
        [DefaultValue(false), Category("Behavior")]
        [Browsable(true)]
        public virtual Boolean EnableCustomPaging
        {
            get
            {
                object obj = base.ViewState["EnableCustomPaging"];
                return (obj == null) ? false : (Boolean)obj;
            }
            set { base.ViewState["EnableCustomPaging"] = value; }
        }

        /// <summary>
        /// The virtual item count for custom paging. This is necessary because the paging mechanism
        /// needs to know how many total items are in our datasource.
        /// </summary>
        [DefaultValue(-1), Category("Behavior")]
        [Browsable(false)]
        public virtual Int32 CustomItemCount
        {
            get
            {
                object obj = base.ViewState["CustomItemCount"];
                return (obj == null) ? -1 : (Int32)obj;
            }
            set { base.ViewState["CustomItemCount"] = value; }
        }

        #endregion Public Properties

        #region Base Overrides

        protected override void OnInit(EventArgs e)
        {
            base.OnInit(e);
            EnableViewState = true;
            if (_useDefaultPageSize)
            {
                base.PageSize = 10;// ((WebFormBase)Page).PageController.RowsPerPage;
            }
        }

        /// <summary>
        /// Initializes the pager
        /// </summary>
        /// <param name="row"></param>
        /// <param name="columnSpan"></param>
        /// <param name="pagedDataSource"></param>
        protected override void InitializePager(GridViewRow row, int columnSpan, PagedDataSource pagedDataSource)
        {
            //Set up the custom paging if we have it enabled.
            if (EnableCustomPaging)
            {
                pagedDataSource.AllowCustomPaging = true;
                pagedDataSource.VirtualCount = CustomItemCount;
                pagedDataSource.CurrentPageIndex = CustomPageIndex;
                PageIndex = CustomPageIndex;
            }

            //Create a table to wrap our pager
            Table table = new Table();
            table.CssClass = FooterStyle.CssClass;
            table.BorderWidth = 0;
            table.Width = Unit.Percentage(100);
            table.Rows.Add(new TableRow());

            //The left-most cell will be a label displaying which records we are on
            TableCell currentRecordsCell = new TableCell();
            currentRecordsCell.HorizontalAlign = HorizontalAlign.Left;
            currentRecordsCell.Width = Unit.Percentage(33);
            currentRecordsCell.Controls.Add(GetCurrentRecordLabel(pagedDataSource.DataSourceCount));
            table.Rows[0].Cells.Add(currentRecordsCell);

            //The middle cell will be the left and right buttons for navigating to other pages
            TableCell currentPageCell = new TableCell();
            currentPageCell.HorizontalAlign = HorizontalAlign.Center;
            currentPageCell.Width = Unit.Percentage(34);
            currentPageCell.Controls.Add(PageBackButton);
            currentPageCell.Controls.Add(Spacer);
            currentPageCell.Controls.Add(GetCurrentPageLabel(pagedDataSource));
            currentPageCell.Controls.Add(Spacer);
            currentPageCell.Controls.Add(PageForwardButton);
            table.Rows[0].Cells.Add(currentPageCell);

            //The right-most cell will be a dropdown for navigating directly to a page
            TableCell goToPageCell = new TableCell();
            goToPageCell.HorizontalAlign = HorizontalAlign.Right;
            goToPageCell.Width = Unit.Percentage(33);
            goToPageCell.Controls.Add(GoToPageLabel);
            goToPageCell.Controls.Add(Spacer);
            goToPageCell.Controls.Add(GetPageDropDown(pagedDataSource));
            table.Rows[0].Cells.Add(goToPageCell);

            //Add the table in a new cell in the row
            row.Controls.AddAt(0, new TableCell());
            row.Cells[0].ColumnSpan = Columns.Count;
            row.Cells[0].Controls.AddAt(0, table);
        }

        /// <summary>
        /// Prepares controls
        /// </summary>
        protected override void PrepareControlHierarchy()
        {
            base.PrepareControlHierarchy();

            #region Sort Arrows

            if (HasControls())
            {
                if (ShowHeader && (!String.IsNullOrEmpty(SortExpression) || !String.IsNullOrEmpty(CustomSortExpression)))
                {
                    Table table = Controls[0] as Table;
                    if (table != null && table.Rows.Count > 0)
                    {
                        //Check the first 2 rows to see if one is a header row.
                        //Have to do this because the very top row could potentially be a pager row
                        GridViewRow row = table.Rows[0] as GridViewRow;
                        if (row.RowType != DataControlRowType.Header)
                        {
                            if (table.Rows.Count > 1)
                            {
                                row = table.Rows[1] as GridViewRow;
                            }
                        }

                        //If we actually have a header row, add the sort arrow to the proper cell.
                        if (row.RowType == DataControlRowType.Header)
                        {
                            foreach (TableCell cell in row.Cells)
                            {
                                DataControlFieldCell fieldCell = cell as DataControlFieldCell;
                                if (fieldCell != null)
                                {
                                    DataControlField field = fieldCell.ContainingField;
                                    if (field != null)
                                    {
                                        if (!String.IsNullOrEmpty(SortExpression) && field.SortExpression == SortExpression)
                                        {
                                            AddSortArrow(cell, SortDirection);
                                            break;
                                        }
                                        else if (!String.IsNullOrEmpty(CustomSortExpression) && field.SortExpression == CustomSortExpression)
                                        {
                                            AddSortArrow(cell, CustomSortDirection);
                                            break;
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }

            #endregion Sort Arrows

            #region Fixed Page Size

            if (FixedPageSize && Rows.Count > 0)
            {
                for (Int32 i = Rows.Count; i < PageSize; i++)
                {
                    GridViewRow row = new GridViewRow(-1, -1, DataControlRowType.DataRow, DataControlRowState.Normal);
                    if (i % 2 == 0)
                    {
                        row.CssClass = RowStyle.CssClass;
                    }
                    else
                    {
                        row.CssClass = AlternatingRowStyle.CssClass;
                    }
                    for (Int32 columns = 0; columns < Columns.Count; columns++)
                    {
                        TableCell cell = new TableCell();
                        cell.Controls.Add(Spacer);
                        row.Controls.Add(cell);
                    }

                    //Add the row before the footer row
                    Controls[0].Controls.AddAt(Controls[0].Controls.Count - 1, row);
                }
            }

            #endregion Fixed Page Size
        }

        /// <summary>
        /// The data source for the control
        /// </summary>
        public override object DataSource
        {
            get
            {
                return base.DataSource;
            }
            set
            {
                base.DataSource = value;
                //Store the value here so it doesn't get reset when the data is bound.
                CustomPageIndex = PageIndex;
            }
        }

        /// <summary>
        /// The size of each page when paging is enabled
        /// </summary>
        public override Int32 PageSize
        {
            get { return base.PageSize; }
            set
            {
                _useDefaultPageSize = false;
                base.PageSize = value;
            }
        }

        #endregion Base Overrides

        #region Protected Methods

        /// <summary>
        /// A label representing the current records shown
        /// </summary>
        protected virtual Label GetCurrentRecordLabel(Int32 dataSourceCount)
        {
            Int32 totalRecords = dataSourceCount;
            Int32 startRecord = -1;
            Int32 endRecord = PageSize * (PageIndex + 1);

            //Figure out the end record
            if (endRecord > totalRecords)
            {
                endRecord = totalRecords;
            }

            //Find the start record
            if (PageIndex == 0)
            {
                startRecord = 1;
            }
            else
            {
                startRecord = (PageIndex * PageSize) + 1;
            }

            //Create the label
            Label label = new Label();
            label.Font.Size = FontUnit.XSmall;
            label.Text = String.Format("Showing {0}-{1} of {2} Records", startRecord, endRecord, totalRecords);

            return label;
        }

        /// <summary>
        /// A label representing the current page
        /// </summary>
        protected virtual Label GetCurrentPageLabel(PagedDataSource dataSource)
        {
            // Calculate the current page number.
            Int32 currentPage = dataSource.CurrentPageIndex + 1;
            Int32 totalPages = dataSource.PageCount;

            //Create the label
            Label label = new Label();
            label.Font.Size = FontUnit.XSmall;
            label.Text = String.Format("Page {0} of {1}", currentPage, totalPages);

            return label;
        }

        /// <summary>
        /// A dropdown for navigating directly to a page
        /// </summary>
        protected virtual DropDownListEx GetPageDropDown(PagedDataSource dataSource)
        {
            DropDownListEx dropDown = new DropDownListEx();
            dropDown.AutoPostBack = true;
            dropDown.ID = "GridViewEx_PageDropDown";
            dropDown.Font.Size = FontUnit.XXSmall;
            for (int i = 0; i < dataSource.PageCount; i++)
            {
                dropDown.Items.Add(new ListItem(Convert.ToString(i + 1), i.ToString()));
            }
            dropDown.SelectedIndex = dataSource.CurrentPageIndex;
            dropDown.SelectedIndexChanged += new EventHandler(pageDropDown_SelectedIndexChanged);
            return dropDown;
        }

        /// <summary>
        /// Add a sort arrow to a cell in the grid
        /// </summary>
        /// <param name="cell">The <see cref="DataControlFieldCell"/> to which we add the arrow</param>
        protected virtual void AddSortArrow(TableCell cell, SortDirection direction)
        {
            String imageUrl = null;

            //Determine which image to display
            if (direction == SortDirection.Ascending)
            {
                imageUrl = SortAscendingImage;
            }
            else
            {
                imageUrl = SortDescendingImage;
            }

            //Add the spacer
            cell.Controls.Add(Spacer);

            //Create the image object and add it to the cell
            Image image = new Image();
            image.BorderStyle = BorderStyle.None;
            image.ImageUrl = imageUrl;
            cell.Controls.Add(image);
        }

        #endregion Protected Methods

        #region Protected Properties

        /// <summary>
        /// The current page index for custom paging. This is necessary because the GridView's PageIndex
        /// property is reset when we re-pull the data.
        /// </summary>
        protected Int32 CustomPageIndex
        {
            get
            {
                object obj = base.ViewState["CustomPageIndex"];
                return (obj == null) ? -1 : (Int32)obj;
            }
            set { base.ViewState["CustomPageIndex"] = value; }
        }

        /// <summary>
        /// A spacer
        /// </summary>
        protected LiteralControl Spacer
        {
            get
            {
                LiteralControl spacer = new LiteralControl("&nbsp;");
                return spacer;
            }
        }

        /// <summary>
        /// A button for navigating to the previous page
        /// </summary>
        protected virtual ImageButton PageBackButton
        {
            get
            {
                ImageButton button = new ImageButton();
                button.ImageUrl = PagerBackImage;
                button.ID = "GridViewEx_PageBackButton";
                button.Attributes.Add("onmouseover", "this.style.cursor='pointer'");
                button.Click += new System.Web.UI.ImageClickEventHandler(pageBackButton_Click);
                return button;
            }
        }

        /// <summary>
        /// A button for navigating to the next page
        /// </summary>
        protected virtual ImageButton PageForwardButton
        {
            get
            {
                ImageButton button = new ImageButton();
                button.ImageUrl = PagerForwardImage;
                button.ID = "GridViewEx_PageForwardButton";
                button.Attributes.Add("onmouseover", "this.style.cursor='pointer'");
                button.Click += new System.Web.UI.ImageClickEventHandler(pageForwardButton_Click);
                return button;
            }
        }

        /// <summary>
        /// A label for navigating directly to a page
        /// </summary>
        protected virtual Label GoToPageLabel
        {
            get
            {
                //Create the label
                Label label = new Label();
                label.Font.Size = FontUnit.XSmall;
                label.Text = "Go to Page";

                return label;
            }
        }

        #endregion Protected Properties

        #region Private Fields

        /// <summary>
        /// Indicates whether or not this grid will use the default page size specified in user options
        /// </summary>
        private Boolean _useDefaultPageSize = true;

        #endregion Private Fields

        #region Events

        /// <summary>
        /// Handles the Click event of the page back button
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected virtual void pageBackButton_Click(object sender, System.Web.UI.ImageClickEventArgs e)
        {
            Int32 newIndex = PageIndex;
            if (newIndex != 0)
            {
                newIndex--;
            }
            else
            {
                newIndex = PageCount - 1;
            }
            this.OnPageIndexChanging(new GridViewPageEventArgs(newIndex));
        }

        /// <summary>
        /// Handles the Click event of the page forward button
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected virtual void pageForwardButton_Click(object sender, System.Web.UI.ImageClickEventArgs e)
        {
            Int32 newIndex = PageIndex;
            if (newIndex != PageCount - 1)
            {
                newIndex++;
            }
            else
            {
                newIndex = 0;
            }
            this.OnPageIndexChanging(new GridViewPageEventArgs(newIndex));
        }

        /// <summary>
        /// Handles the SelectedIndexChanged event of the page dropdown.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected virtual void pageDropDown_SelectedIndexChanged(object sender, EventArgs e)
        {
            DropDownListEx list = sender as DropDownListEx;
            if (list != null)
            {
                this.OnPageIndexChanging(new GridViewPageEventArgs(list.SelectedIndex));
            }
        }       
        #endregion Events
    }
}
